﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using Scintilla4Net;
using System.Xml.Serialization;
using System.Collections;
using System.IO;

namespace EbnfStudio.Controls
{
    /// <summary>
    /// Simple editor based off Scintilla.NET
    /// </summary>
    public class Editor : Scintilla
    {
        // the comment style id
        public enum Styles
        {
            Default = STYLE_DEFAULT,
            Comment = 1,
            Literal,
            Operator,
        }

        /// <summary>
        /// Simple class for style info
        /// </summary>
        public class StyleInfo
        {
            // style name
            [XmlAttribute("Name")]
            public Styles Number;
            // style font
            public string Font;
            // font size
            public int Size;
            // is bold ?
            public bool Bold;
            // is italic ?
            public bool Italic;
            // is underlined ?
            public bool Underlined;

            // the foreground color
            [XmlIgnore()]
            public Color ForeColor;
            // Fg color
            [XmlElement("ForeColor")]
            public string XmlForeColor
            {
                get { return SerializeColor(this.ForeColor); }
                set { ForeColor = DeserializeColor(value); }
            }

            [XmlIgnore()]
            public Color BackColor;
            // Fg color
            [XmlElement("BackColor")]
            public string XmlBackColor
            {
                get { return SerializeColor(this.BackColor); }
                set { BackColor = DeserializeColor(value); }
            }

            // used for color serialization
            public enum ColorFormat
            {
                Name, ARGB
            }
            public string SerializeColor(Color color)
            {
                if (color.IsNamedColor)
                    return string.Format("{0}:{1}",
                        ColorFormat.Name, color.Name);
                else
                    return string.Format("{0}:{1}:{2}:{3}:{4}",
                        ColorFormat.ARGB,
                        color.A, color.R, color.G, color.B);
            }
            public Color DeserializeColor(string color)
            {
                byte a, r, g, b;

                string[] pieces = color.Split(new char[] { ':' });

                ColorFormat colorType = (ColorFormat)
                    Enum.Parse(typeof(ColorFormat), pieces[0], true);

                switch (colorType)
                {
                    case ColorFormat.Name:
                        return Color.FromName(pieces[1]);

                    case ColorFormat.ARGB:
                        a = byte.Parse(pieces[1]);
                        r = byte.Parse(pieces[2]);
                        g = byte.Parse(pieces[3]);
                        b = byte.Parse(pieces[4]);

                        return Color.FromArgb(a, r, g, b);
                }
                return Color.Empty;
            }
        }

        // Shopping list class which will be serialized
        [XmlRoot("Styles")]
        public class StyleConfig
        {
            // store data
            private ArrayList listShopping = new ArrayList();

            /// <summary>
            /// Get items stored
            /// </summary>
            [XmlElement("Style")]
            public StyleInfo[] Styles
            {
                get
                {
                    StyleInfo[] items = new StyleInfo[listShopping.Count];
                    listShopping.CopyTo(items);
                    return items;
                }
                set
                {
                    if (value == null) return;
                    StyleInfo[] items = (StyleInfo[])value;
                    listShopping.Clear();
                    foreach (StyleInfo item in items)
                        listShopping.Add(item);
                }
            }
        }

        // the config
        static StyleConfig Config = null;

        /// <summary>
        /// Parser
        /// </summary>
        public Parser parser = new Parser();

        /// <summary>
        /// constructor
        /// </summary>
        public Editor()
        {
            this.Dock = DockStyle.Fill;

            // set container lexer
            SetLexer(SCLEX_CONTAINER);

            // Load config
            if (Config == null)
            {
                var path = Application.StartupPath;
                XmlSerializer serializer = new XmlSerializer(typeof(StyleConfig));
                TextReader reader = new StreamReader(path + @"\styles.xml");
                Config = (StyleConfig)serializer.Deserialize(reader);
                reader.Close();
            }

            // apply styles
            var styles = Config.Styles;
            foreach (var style in styles) this.SetStyle(style);

            // misc config
            SetCaretWidth(2);
            SetMarginWidthN(0, TextWidth(STYLE_LINENUMBER, "100"));

            Modified += (object sender, ScintillaEventArgs args) =>
            {
                parser.Parse(this.Text);
            };

            // subscribe to style needed event
            StyleNeeded += OnStyleNeeded;
        }


        /// <summary>
        /// Set style
        /// </summary>
        private void SetStyle(StyleInfo style)
        {
            // font
            if (style.Font != "")
                StyleSetFont((int)style.Number, style.Font);
            if (style.Size != 0)
                StyleSetSize((int)style.Number, style.Size);
            StyleSetFore((int)style.Number, style.ForeColor);
            StyleSetBack((int)style.Number, style.BackColor);
            StyleSetBold((int)style.Number, style.Bold);
            StyleSetItalic((int)style.Number, style.Italic);
            StyleSetUnderline((int)style.Number, style.Underlined);

            if ((int)style.Number == STYLE_DEFAULT)
                StyleClearAll();
        }


        /// <summary>
        /// Apply style
        /// </summary>
        void OnStyleNeeded(object owner, ScintillaEventArgs args)
        {
            int line = LineFromPosition(GetEndStyled());
            int start = PositionFromLine(line);
            int end = args.Native.position;

            // start styling from the beginning of the line
            StartStyling(start, 0x1f);
            /*
            foreach (var token in parser.Tokens)
            {
                var tl = token.Line - 1;
                if (tl < line) continue;
                int lp = PositionFromLine(tl);
                SetStyling(lp - start, (int)Styles.Default);
                SetStyling(token.Column - lp, (int)Styles.Default);
                start = lp;
                switch (token.Id)
                {
                    case TokenClass.Literal :
                        SetStyling(token.Size, (int)Styles.Literal);
                        break;
                }
            }
            */

            /*
            bool inComment = false;
            bool inLiteral = false;
            char litClose = '\0';

            // detect if this is a multiline comment?
            if (start > 0)
            {
                inComment = GetStyleAt(start - 1) == (int)Styles.Comment;
            }

            // iterate over the chars
            for (int i = start; i < end; i++)
            {
                char ch = (char)GetCharAt(i);
                char nCh = (char)GetCharAt(i+1);

                // comments
                if (!inComment && ch == '(' && nCh == '*')
                {
                    inComment = true;
                    SetStyling(i - start, (int)Styles.Default);
                    start = i++;
                    continue;
                }
                if (inComment && ch == '*' && nCh == ')')
                {
                    inComment = false;
                    i++;
                    SetStyling(i - start + 1, (int)Styles.Comment);
                    start = i+1;
                    continue;
                }
                if (inComment) continue;

                // literals
                if (inLiteral)
                {
                    if (ch == litClose && nCh == litClose)
                    {
                        i++;
                    }
                    else if (ch == '\\' && (nCh == '\\' || nCh == '\'' || nCh == '"'))
                    {
                        i++;
                    }
                    else if (ch == litClose || ch == '\n')
                    {
                        inLiteral = false;
                        SetStyling(i - start + 1, (int)Styles.Literal);
                        start = i + 1;
                        continue;
                    }
                    continue;
                }
                else if (ch == '"' || ch == '\'')
                {
                    litClose = ch;
                    inLiteral = true;
                    SetStyling(i - start, 0);
                    start = i;
                    continue;
                }

                // operators
                if (ch == '=' || ch == ':' || ch == '.' || ch == ';'
                    || ch == '(' || ch == ')' || ch == '[' || ch == ']'
                    || ch == '{' || ch == '}' || ch == '|')
                {
                    SetStyling(i - start, 0);
                    SetStyling(1, (int)Styles.Operator);
                    start = i + 1;
                }
            }

            // set default styling
            if (inComment)
            {
                SetStyling(end - start, (int)Styles.Comment);
            }
            else if (inLiteral)
            {
                SetStyling(end - start, (int)Styles.Literal);
            }
            else
            {
                SetStyling(end - start, (int)Styles.Default);
            }
            */
        }
    }
}
